/* 
 * File:   ObjectModel.cpp
 * Author: Pie
 * 
 * Created on April 25, 2011, 4:09 PM
 */

#include <ios>
#include <fstream>
#include <iostream>

#include "Scene.h"
#include "MathUtil.h"

void color_to_float(float f[4], const aiColor4D* c) {
    f[0] = c->r;
    f[1] = c->g;
    f[2] = c->b;
    f[3] = c->a;
}

void zero(float f[4]) {
    f[0] = 0;
    f[1] = 0;
    f[2] = 0;
    f[3] = 0;
}

ObjectNode::ObjectNode(aiNode* node, ObjectNode* parent) {
    _name = node->mName.data;
    _parent = parent;
    copyMatrix(&(node->mTransformation), &_transform);

    _num_mesh = node->mNumMeshes;
    _mesh = new int[_num_mesh];
    for (int i = 0; i < _num_mesh; ++i) {
        _mesh[i] = node->mMeshes[i];
    }
    _num_ch = node->mNumChildren;
    _children = new ObjectNode*[_num_ch];
    for (int i = 0; i < node->mNumChildren; ++i) {
        _children[i] = new ObjectNode(node->mChildren[i], this);
    }
}

void ObjectNode::write(ofstream* out) {

    //offset
    out->write(reinterpret_cast<char*> (&(_transform.a1)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_transform.a2)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_transform.a3)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_transform.a4)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_transform.b1)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_transform.b2)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_transform.b3)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_transform.b4)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_transform.c1)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_transform.c2)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_transform.c3)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_transform.c4)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_transform.d1)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_transform.d2)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_transform.d3)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_transform.d4)), sizeof (float));

    //Num Mesh
    
    std::cout << "Number of Meshes: " << _num_mesh << "\n";
    out->write(reinterpret_cast<char*> (&_num_mesh), sizeof (int));
    if (_num_mesh > 0) {
        out->write(reinterpret_cast<char*> (_mesh), sizeof (int) * _num_mesh);
    }

    //Num Children
    std::cout << "Number of Children: " << _num_ch << "\n";
    out->write(reinterpret_cast<char*> (&_num_ch), sizeof (int));
    for (int i = 0; i < _num_ch; ++i) {
        _children[i]->write(out);
    }

}

ObjectNode::~ObjectNode() {
    //delete _transform;
    delete [] _mesh;
    for (int i = 0; i < _num_ch; ++i) {
        delete _children[i];
    }
    delete [] _children;
}

ObjectBone::ObjectBone(aiBone* bone) {
    _name = bone->mName.data;
    _offset = bone->mOffsetMatrix;

    _num_w = bone->mNumWeights;
    _weights = new aiVertexWeight[_num_w];
    for (int i = 0; i < _num_w; ++i) {
        _weights[i] = bone->mWeights[i];
    }
}

ObjectBone::~ObjectBone() {
    delete [] _weights;
}

void ObjectBone::write(ofstream* out) {

    //offset
    out->write(reinterpret_cast<char*> (&(_offset.a1)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_offset.a2)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_offset.a3)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_offset.a4)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_offset.b1)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_offset.b2)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_offset.b3)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_offset.b4)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_offset.c1)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_offset.c2)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_offset.c3)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_offset.c4)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_offset.d1)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_offset.d2)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_offset.d3)), sizeof (float));
    out->write(reinterpret_cast<char*> (&(_offset.d4)), sizeof (float));

    //Weights Num
    out->write(reinterpret_cast<char*> (&_num_w), sizeof (int));
    for (int i = 0; i < _num_w; ++i) {
        out->write(reinterpret_cast<char*> (&_weights[i].mVertexId), sizeof (int));
        out->write(reinterpret_cast<char*> (&_weights[i].mWeight), sizeof (float));
    }

}

ObjectChannel::ObjectChannel(aiNodeAnim* chan) {
    _name = chan->mNodeName.data;
    _pre = chan->mPreState;
    _post = chan->mPostState;

    _num_p = chan->mNumPositionKeys;
    _pos_keys = new aiVectorKey[_num_p];
    for (int i = 0; i < _num_p; ++i) {
        _pos_keys[i] = chan->mPositionKeys[i];
    }

    _num_s = chan->mNumScalingKeys;
    _scale_keys = new aiVectorKey[_num_s];
    for (int i = 0; i < _num_s; ++i) {
        _scale_keys[i] = chan->mScalingKeys[i];
    }

    _num_r = chan->mNumRotationKeys;
    _rotate_keys = new aiQuatKey[_num_r];
    for (int i = 0; i < _num_r; ++i) {
        _rotate_keys[i] = chan->mRotationKeys[i];
    }
}

ObjectChannel::~ObjectChannel() {
    delete [] _pos_keys;
    delete [] _scale_keys;
    delete [] _rotate_keys;
}

void ObjectChannel::write(ofstream* out) {

    out->write(reinterpret_cast<char*> (&_pre), sizeof (int));
    out->write(reinterpret_cast<char*> (&_post), sizeof (int));

    out->write(reinterpret_cast<char*> (&_num_p), sizeof (int));
    for (int i = 0; i < _num_p; ++i) {
        float t = (float) _pos_keys[i].mTime;
        out->write(reinterpret_cast<char*> (&t), sizeof (float));
        out->write(reinterpret_cast<char*> (&_pos_keys[i].mValue.x), sizeof (float));
        out->write(reinterpret_cast<char*> (&_pos_keys[i].mValue.y), sizeof (float));
        out->write(reinterpret_cast<char*> (&_pos_keys[i].mValue.z), sizeof (float));
    }

    out->write(reinterpret_cast<char*> (&_num_s), sizeof (int));
    for (int i = 0; i < _num_s; ++i) {
        float t = (float) _scale_keys[i].mTime;
        out->write(reinterpret_cast<char*> (&t), sizeof (float));
        out->write(reinterpret_cast<char*> (&_scale_keys[i].mValue.x), sizeof (float));
        out->write(reinterpret_cast<char*> (&_scale_keys[i].mValue.y), sizeof (float));
        out->write(reinterpret_cast<char*> (&_scale_keys[i].mValue.z), sizeof (float));
    }

    out->write(reinterpret_cast<char*> (&_num_r), sizeof (int));
    for (int i = 0; i < _num_r; ++i) {
        float t = (float) _rotate_keys[i].mTime;
        out->write(reinterpret_cast<char*> (&t), sizeof (float));
        out->write(reinterpret_cast<char*> (&_rotate_keys[i].mValue.x), sizeof (float));
        out->write(reinterpret_cast<char*> (&_rotate_keys[i].mValue.y), sizeof (float));
        out->write(reinterpret_cast<char*> (&_rotate_keys[i].mValue.z), sizeof (float));
        out->write(reinterpret_cast<char*> (&_rotate_keys[i].mValue.w), sizeof (float));
    }
}

ObjectAnimation::ObjectAnimation(aiAnimation* anim) {
    _name = anim->mName.data;
    _duration = (int) anim->mDuration;
    _tickps = (int) anim->mTicksPerSecond;
    _num_chan = anim->mNumChannels;
    _chan = new ObjectChannel*[_num_chan];
    for (int i = 0; i < _num_chan; ++i) {
        _chan[i] = new ObjectChannel(anim->mChannels[i]);
    }
}

ObjectAnimation::~ObjectAnimation() {
    for (int i = 0; i < _num_chan; ++i) {
        delete _chan[i];
    }
    delete [] _chan;
}

void ObjectAnimation::write(ofstream* out) {

    out->write(reinterpret_cast<char*> (&_duration), sizeof (int));
    out->write(reinterpret_cast<char*> (&_tickps), sizeof (int));
    out->write(reinterpret_cast<char*> (&_num_chan), sizeof (int));
    for (int i = 0; i < _num_chan; ++i) {
        _chan[i]->write(out);
    }

}

ObjectVertex::ObjectVertex(aiVector3D* pos, aiVector3D* norm, aiVector3D* uv) :
_norm(0), _uv(0) {
    _pos = new aiVector3D(pos->x, pos->y, pos->z);
    if (norm) _norm = new aiVector3D(norm->x, norm->y, norm->z);
    if (uv) _uv = new aiVector2D(uv->x, uv->y);
}

ObjectVertex::~ObjectVertex() {
    if (_pos) delete _pos;
    if (_norm) delete _norm;
    if (_uv) delete _uv;
}

void ObjectVertex::write(ofstream* out) {

    out->write(reinterpret_cast<char*> (&_pos->x), sizeof (float));
    out->write(reinterpret_cast<char*> (&_pos->y), sizeof (float));
    out->write(reinterpret_cast<char*> (&_pos->z), sizeof (float));
    if (_norm) {
        out->write(reinterpret_cast<char*> (&_norm->x), sizeof (float));
        out->write(reinterpret_cast<char*> (&_norm->y), sizeof (float));
        out->write(reinterpret_cast<char*> (&_norm->z), sizeof (float));
    } else {
        float zero[] = {0.0f, 0.0f, 0.0f};
        out->write(reinterpret_cast<char*> (&zero), sizeof (float) * 3);
    }
    if (_uv) {
        out->write(reinterpret_cast<char*> (&_uv->x), sizeof (float));
        out->write(reinterpret_cast<char*> (&_uv->y), sizeof (float));
    } else {
        float zero[] = {0.0f, 0.0f};
        out->write(reinterpret_cast<char*> (&zero), sizeof (float) * 2);
    }


}

void ObjectVertex::to_vbo(Vbo* vbo) {
    vbo->x = _pos->x;
    vbo->y = _pos->y;
    vbo->z = _pos->z;
    if (_norm) {
        vbo->nx = _norm->x;
        vbo->ny = _norm->y;
        vbo->nz = _norm->z;
    } else {
        vbo->nx = 0;
        vbo->ny = 1;
        vbo->nz = 0;
    }
    if (_uv) {
        vbo->u = _uv->x;
        vbo->v = _uv->y;
    } else {
        vbo->u = 0;
        vbo->v = 0;
    }
}

ObjectFace::ObjectFace(int _0, int _1, int _2) {
    this->_0 = _0;
    this->_1 = _1;
    this->_2 = _2;
}

ObjectFace::~ObjectFace() {
}

void ObjectFace::write(ofstream* out) {
    out->write(reinterpret_cast<char*> (&_0), sizeof (int));
    out->write(reinterpret_cast<char*> (&_1), sizeof (int));
    out->write(reinterpret_cast<char*> (&_2), sizeof (int));
}

ObjectMesh::ObjectMesh(const aiMesh* mesh) :
_mat(0) {
    _name = mesh->mName.data;
    _mat = mesh->mMaterialIndex;
    bool n = mesh->HasNormals();
    bool t = mesh->HasTextureCoords(0);

    _num_v = mesh->mNumVertices;
    _vert = new ObjectVertex*[_num_v];
    for (int i = 0; i < _num_v; ++i) {
        _vert[i] = new ObjectVertex(&(mesh->mVertices[i]),
                (n ? &(mesh->mNormals[i]) : 0),
                (t ? &(mesh->mTextureCoords[0][i]) : 0));
    }

    _num_f = mesh->mNumFaces;
    _face = new ObjectFace*[_num_f];
    //TODO: FACE is unnecessary, resolve into mesh
    for (int i = 0; i < _num_f; i++) {
        _face[i] = new ObjectFace(mesh->mFaces[i].mIndices[0], mesh->mFaces[i].mIndices[1], mesh->mFaces[i].mIndices[2]);

    }

    _num_b = mesh->mNumBones;
    _bone = new ObjectBone*[_num_b];
    for (int i = 0; i < _num_b; ++i) {
        _bone[i] = new ObjectBone(mesh->mBones[i]);
    }
}

ObjectMesh::~ObjectMesh() {
    for (int i = 0; i < _num_v; ++i) {
        delete _vert[i];
    }
    for (int i = 0; i < _num_b; ++i) {
        delete _bone[i];
    }
    delete [] _vert;
    delete [] _face;
    delete [] _bone;
}

void ObjectMesh::write(ofstream* out) {

    //Material Index
    std::cout << "Material Index: " << _mat << std::endl;
    out->write(reinterpret_cast<char*> (&_mat), sizeof (int));

    //Num Vertices
    std::cout << "Num Vertices: " << _num_v << std::endl;
    out->write(reinterpret_cast<char*> (&_num_v), sizeof (int));
    for (int i = 0; i < _num_v; ++i) {
        _vert[i]->write(out);
    }
    std::cout << "Current size: " << out->tellp() << "\n";

    //Num Faces
    std::cout << "Num Faces: " << _num_f << " (" << sizeof (_face) << ") " << std::endl;

    out->write(reinterpret_cast<char*> (&_num_f), sizeof (int));
    for (int i = 0; i < _num_f; ++i) {
        _face[i]->write(out);
    }

    std::cout << "Current size: " << out->tellp() << "\n";

    //Num Bones
    std::cout << "Num Bones: " << _num_b << std::endl;
    out->write(reinterpret_cast<char*> (&_num_b), sizeof (int));
    for (int i = 0; i < _num_b; ++i) {
        _bone[i]->write(out);
    }
}

ObjectMaterial::ObjectMaterial(const aiMaterial* mtl) {

    aiColor4D diff;
    aiColor4D spec;
    aiColor4D amb;
    aiColor4D emis;
    float sh;
    float st;
    aiString path;

    if (AI_SUCCESS == mtl->GetTexture(aiTextureType_DIFFUSE, 0, &path)) {
        if (path.length > 0) {
            _texture = path.data;
        } else {
            _texture = "";
        }
    } else {
        _texture = "";
    }

    if (AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_DIFFUSE, &diff)) {
        color_to_float(_diffuse, &diff);
    } else {
        zero(_diffuse);
    }
    if (AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_SPECULAR, &spec)) {
        color_to_float(_specular, &spec);
    } else {
        zero(_specular);
    }
    if (AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_AMBIENT, &amb)) {
        color_to_float(_ambient, &amb);
    } else {
        zero(_ambient);
    }
    if (AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_EMISSIVE, &emis)) {
        color_to_float(_emission, &emis);
    } else {
        zero(_emission);
    }

    unsigned int max = 1;
    if (AI_SUCCESS == aiGetMaterialFloatArray(mtl, AI_MATKEY_SHININESS, &sh, &max) &&
            AI_SUCCESS == aiGetMaterialFloatArray(mtl, AI_MATKEY_SHININESS_STRENGTH, &st, &max)) {
        _shininess = sh * st;
    } else {
        _shininess = 0;
    }
}

void ObjectMaterial::write(ofstream* out) {

    //Diffuse
    out->write(reinterpret_cast<char*> (&_diffuse), sizeof (_diffuse));
    //Specular
    out->write(reinterpret_cast<char*> (&_specular), sizeof (_specular));
    //Ambient
    out->write(reinterpret_cast<char*> (&_ambient), sizeof (_ambient));
    //Emissive
    out->write(reinterpret_cast<char*> (&_emission), sizeof (_emission));
    //Shininess
    out->write(reinterpret_cast<char*> (&_shininess), sizeof (float));
    //Texture index
    out->write(reinterpret_cast<char*> (&_texture_index), sizeof (int));

}

SceneModel::SceneModel(const aiScene* scene) :
_node(0) {
    _num_anim = scene->mNumAnimations;
    _anim = new ObjectAnimation*[_num_anim];
    for (int i = 0; i < _num_anim; ++i) {
        _anim[i] = new ObjectAnimation(scene->mAnimations[i]);
    }
    _num_matl = scene->mNumMaterials;
    _matl = new ObjectMaterial*[_num_matl];
    std::vector<string> textures;
    for (int i = 0; i < _num_matl; ++i) {
        _matl[i] = new ObjectMaterial(scene->mMaterials[i]);
        bool newTex = true;
        for (int j = 0; j < textures.size() && newTex; ++j) {
            if (textures.at(j) == _matl[i]->_texture) {
                _matl[i]->_texture_index = j;
                newTex = false;
            }
        }
        if (newTex) {
            _matl[i]->_texture_index = textures.size();
            textures.push_back(_matl[i]->_texture);
        }
    }
    _num_tex = textures.size();
    _textures = new string[_num_tex];
    for (int i = 0; i < _num_tex; ++i) {
        _textures[i] = textures.at(i);
    }

    _num_mesh = scene->mNumMeshes;
    _mesh = new ObjectMesh*[_num_mesh];
    for (int i = 0; i < _num_mesh; ++i) {
        _mesh[i] = new ObjectMesh(scene->mMeshes[i]);
    }
    _node = new ObjectNode(scene->mRootNode);
}

SceneModel::~SceneModel() {
    delete _node;
    for (int i = 0; i < _num_anim; ++i) {
        delete _anim[i];
    }
    delete [] _anim;
    for (int i = 0; i < _num_matl; ++i) {
        delete _matl[i];
    }
    delete [] _matl;
    for (int i = 0; i < _num_mesh; ++i) {
        delete _mesh[i];
    }
    delete [] _mesh;
}

void SceneModel::write(ofstream* out) {

    //Texture
    //    char z[] = {0};
    //    
    //    _num_tex = 2;
    //    _textures = new string[2];
    //    _textures[0] = "Pie";
    //    _textures[1] = "Man";

    std::cout << "Outputting " << _num_tex << " Textures\n";
    out->write(reinterpret_cast<char*> (&_num_tex), sizeof (int));
    for (int i = 0; i < _num_tex; ++i) {
        string t = _textures[i];
        int length = t.length();
        std::cout << i << ": (" << length << ") " << t << "\n";
        out->write(reinterpret_cast<char*> (&length), sizeof (int));
        out->write(t.c_str(), length);
    }
    std::cout << "Current size: " << out->tellp() << "\n\n";

    //Material
    std::cout << "Outputting " << _num_matl << " Materials\n";
    out->write(reinterpret_cast<char*> (&_num_matl), sizeof (int));
    for (int i = 0; i < _num_matl; ++i) {
        _matl[i]->write(out);
    }
    std::cout << "Current size: " << out->tellp() << "\n\n";

    //Mesh
    std::cout << "Outputting " << _num_mesh << " Meshes\n";
    out->write(reinterpret_cast<char*> (&_num_mesh), sizeof (int));
    for (int i = 0; i < _num_mesh; ++i) {
        _mesh[i]->write(out);
    }
    std::cout << "Current size: " << out->tellp() << "\n\n";

    //Anim
    std::cout << "Outputting " << _num_anim << " Animations\n";
    out->write(reinterpret_cast<char*> (&_num_anim), sizeof (int));
    for (int i = 0; i < _num_anim; ++i) {
        _anim[i]->write(out);
    }
    std::cout << std::endl;

    std::cout << "Writing root segment\n";
    _node->write(out);
}

SceneNode::SceneNode() {
    _parent = 0;
    _scene = 0;
}

SceneNode::~SceneNode() {
    delete _scene;
    for (vector<SceneNode*>::iterator it = _children.begin(); it != _children.end(); it++) {
        delete *it;
    }
}

void SceneNode::load(SceneModel* scene) {
    _scene = scene;
}

void SceneNode::add_child(SceneNode* child) {
    _children.push_back(child);
    child->set_parent(this);
}

void SceneNode::set_parent(SceneNode* parent) {
    _parent = parent;
}

void SceneNode::write(const string& file) {
    ofstream output(file.c_str(), ios::out | ios::binary);

    char buf[2];
    buf[0] = 0;
    buf[1] = 1;
    output.write(buf, sizeof (buf));
    std::cout << "Version 0.1\n\n";

    _scene->write(&output);

    output.close();
}